<template>
  <span class="my-popover__wrapper">
    <transition name="my-popover-fade">
      <div
        class="my-popover"
        :class="['my-popover--'+position]"
        :style="popoverStyle"
        ref="popover"
        v-show="show"
      >
        <div class="my-popover__arrow"></div>
        <div class="my-popover__title" v-if="title">{{title}}</div>
        <div class="my-popover__content">
          <slot></slot>
        </div>
      </div>
    </transition>
    <slot name="trigger"></slot>
  </span>
</template>

<script>
export default {
  name: "MyPopover",
  props: {
    title: {
      type: String,
      default: "",
    },
    position: {
      type: String,
      default: "top",
      validator(val) {
        return ["top", "bottom", "left", "right"].indexOf(val) >= 0;
      },
    },
    offset: {
      type: Number,
      default: 0,
    },
    trigger: {
      type: String,
      default: "hover",
      validator(val) {
        return ["hover", "click"].indexOf(val) >= 0;
      },
    },
  },
  computed: {
    popoverOffset() {
      return this.offset + 18;
    },
  },
  data() {
    return {
      show: false,
      $trigger: null,
      popoverStyle: {},
    };
  },
  mounted() {
    this.$trigger = this.$slots.trigger[0].elm;
    document.body.appendChild(this.$refs.popover);
    this.addListeners();
  },
  beforeDestroy() {
    document.body.removeChild(this.$refs.popover);
    this.removeListeners();
  },
  methods: {
    addListeners() {
      if (this.trigger === "click") {
        this.$trigger.addEventListener("click", this.handleClick);
      } else {
        this.$trigger.addEventListener("mouseenter", this.open);
        this.$trigger.addEventListener("mouseleave", this.close);
      }
      window.addEventListener("resize", this.calcPopoverStyle);
    },
    removeListeners() {
      if (this.trigger === "click") {
        this.$trigger.removeEventListener("click", this.handleClick);
      } else {
        this.$trigger.removeEventListener("mouseenter", this.open);
        this.$trigger.removeEventListener("mouseleave", this.close);
      }
      window.removeEventListener("resize", this.calcPopoverStyle);
    },
    calcPopoverStyle() {
      let { position, show, $trigger, popoverOffset } = this;
      if (!show) return;
      let $popover = this.$refs.popover;
      let {
        width: tWidth,
        height: tHeight,
        top: tTop,
        left: tLeft,
      } = $trigger.getBoundingClientRect();
      let { height: pHeight, width: pWidth } = $popover.getBoundingClientRect();
      let { scrollY, scrollX } = window;
      switch (position) {
        case "top":
          return (this.popoverStyle = {
            top: `${tTop + scrollY - pHeight - popoverOffset}px`,
            left: `${tLeft + scrollX + tWidth / 2 - pWidth / 2}px`,
          });
        case "left":
          return (this.popoverStyle = {
            top: `${tTop + scrollY + tHeight / 2 - pHeight / 2}px`,
            left: `${tLeft + scrollX - pWidth - popoverOffset}px`,
          });
        case "right":
          return (this.popoverStyle = {
            top: `${tTop + scrollY + tHeight / 2 - pHeight / 2}px`,
            left: `${tWidth + scrollX + tLeft + popoverOffset}px`,
          });
        case "bottom":
          return (this.popoverStyle = {
            top: `${tHeight + scrollY + tTop + popoverOffset}px`,
            left: `${tLeft + scrollX + tWidth / 2 - pWidth / 2}px`,
          });
      }
    },
    handleClick(event) {
      if (this.$trigger.contains(event.target)) {
        if (this.show) {
          this.close();
        } else {
          this.open();
        }
      }
    },
    handleClickDocument(e) {
      if (this.trigger === "hover") return;
      if (
        this.$trigger &&
        (this.$trigger === e.target || this.$trigger.contains(e.target))
      ) {
        return;
      }
      if (
        this.$refs.popover &&
        (this.$refs.popover === e.target ||
          this.$refs.popover.contains(e.target))
      ) {
        return;
      }
      this.close();
    },
    open() {
      this.show = true;
      this.$nextTick(() => {
        this.calcPopoverStyle();
        document.addEventListener("click", this.handleClickDocument);
      });
    },
    close() {
      this.show = false;
      this.$nextTick(() => {
        document.removeEventListener("click", this.handleClickDocument);
      });
    },
  },
};
</script>

<style lang="scss">
.my-popover {
  z-index: 1080;
  position: absolute;
  top: 0px;
  left: 0px;
  max-width: 300px;
  will-change: top, left;
  font-size: 12px;
  line-height: 1.5;
  word-wrap: break-word;
  background-color: #ffffff;
  border: 1px solid #ffffff;
  border-radius: 6px;
  box-shadow: 0px 0px 20px 0px rgb(0 0 0 / 15%);
  transition: opacity 0.15s linear;

  .my-popover__arrow,
  .my-popover__arrow::after {
    position: absolute;
    width: 0;
    height: 0;
    border-style: solid;
    border-color: transparent;
    border-width: 9px;
  }

  &.my-popover--top {
    .my-popover__arrow {
      left: calc(50% - 9px);
      bottom: -18px;
      border-top-color: #ffffff;
    }
  }

  &.my-popover--bottom {
    .my-popover__arrow {
      left: calc(50% - 9px);
      top: -18px;
      border-bottom-color: #ffffff;
    }
  }

  &.my-popover--left {
    .my-popover__arrow {
      top: calc(50% - 9px);
      right: -16px;
      border-left-color: #ffffff;
    }
  }

  &.my-popover--right {
    .my-popover__arrow {
      top: calc(50% - 9px);
      left: -16px;
      border-right-color: #ffffff;
    }
  }
}

.my-popover__title {
  border-bottom: 1px solid #ebedf3;
  color: #181c32;
  padding: 14px;
  font-size: 13px;
}

.my-popover__content {
  padding: 15px;
  color: #3f4254;
}
.my-popover-fade-enter,
.my-popover-fade-leave-active {
  opacity: 0;
}
</style>